#include "kernel.h"
#include "ipc.h"
#include "mm.h"


/**
 * The initialize_message_passing() initializes the  owner and port_id of the 
 * port_table.
 */
void initialize_message_passing(void) {
	LOCK4WRITE(&port_table_lock);
	int i;
	for (i = 0; i < sizeof (port_table) / sizeof (port_table[0]); i++) {
		port_table[i].owner = -1; // owner -1 is free mailbox
		port_table[i].port_id = i;
	}
	//kprints("Kernel: Init port_table");
	RELEASE_LOCK(&port_table_lock);
	return;
}

/**
 * The kcreate_mailbox() system call sets up a mailbox listining on port port_id. 
 * When there is an incomming message the handler is invoked.
 * @returns True if mailbox could be created, false otherwise.
 */

int kcreate_mailbox(void (*handler)(void), int port_id) {
	// Check of the port_id is in range.
	if (port_id >= MAX_NUMBER_OF_PORTS || port_id < 0) {
		return ERROR;
	}
	int report = ALL_OK;
	LOCK4WRITE(&port_table_lock);
	// Allocate mailbox if free, otherwise return Error.
	if (port_table[port_id].owner == -1) {
		port_table[port_id].handler = handler;
		port_table[port_id].msg_queue.head = 0;
		port_table[port_id].msg_queue.tail = 0;
/*
		// DEBUG
		kprints("Kernel: kcreate_mailbox: PID: ");
		kprinthex(kgetpid());
		kprints("\n\0");
		// DEBUG
*/
		port_table[port_id].owner = kgetpid();
/*
		kprints("Kernel: port owner: ");
		kprinthex(port_table[port_id].owner);
		kprints("\n\0");
*/
	} else {
		report = ERROR;
	}
	RELEASE_LOCK(&port_table_lock);

	return report;
}

/**
 * The kdelete_mailbox() deletes the mailbox of a process.
 * Only the owner process of a mailbox can delete it.
 * @returns ALL_OK if the mailbox could be deleted, ERROR otherwise.
 */

int kdelete_mailbox(int port_id) {
	// check if port_id is in range.
	if (port_id >= MAX_NUMBER_OF_PORTS || port_id < 0) {
		return ERROR;
	}
	int report = ALL_OK;
	LOCK4WRITE(&port_table_lock);
	// delete mailbox if calling process is owner of the mailbox.
	if (port_table[port_id].owner == kgetpid()) {
		port_table[port_id].owner = -1;
	} else {
		report = ERROR;
	}
	RELEASE_LOCK(&port_table_lock);

	return report;
}

int ksend_msg(struct message* m) {
	// Check if the port numbers are in range
	if ((m->dest_port >= MAX_NUMBER_OF_PORTS || m->dest_port < 0)
			|| (m->src_port >= MAX_NUMBER_OF_PORTS || m->src_port < 0)) {
		return ERROR;
	}
	LOCK4WRITE(&port_table_lock);
	const int src_owner = port_table[m->src_port].owner;
	// release port_table_lock
	// Check if the calling process is owner of the src_port.
	if(src_owner != kgetpid()){
		RELEASE_LOCK(&port_table_lock);
		return ERROR;
	}
	const int dest_owner = port_table[m->dest_port].owner;
	const int head = port_table[m->dest_port].msg_queue.head;
	const int tail = port_table[m->dest_port].msg_queue.tail;
	
	int report = ALL_OK;
	int new_tail = 0;
	// Check if there is room in the queue
	if (tail == MAX_NUMBER_OF_WAITING_MESSAGES - 1) {
		if (head == 0) {
			// The port_table is full, return error.
			report = 1;
		} else {
			new_tail = 0;
		}
	} else {
		if (tail + 1 == head) {
			// The port_table is full, return error.
			report = 2;
		} else {
			new_tail = tail + 1;
		}
	}
	if(report == ALL_OK){
		port_table[m->dest_port].msg_queue.messages[tail] = *m;
		// Allocate memory for the message to be copied to.
		char* message_data = (char*)kalloc(m->size,dest_owner,ALLOCATE_FLAG_READONLY);
		if(message_data != -1){
			int i;
			// Copy the message from the sending process memory to the receiving process memory.
			for(i = 0; i < m->size; i++){
				*(message_data + i) = *(m->data+i);
			}
			port_table[m->dest_port].msg_queue.messages[tail].data = message_data;
			if(port_table[m->dest_port].handler != 0){
				long stack_end = kalloc(4096, dest_owner,0);
				if(stack_end != -1){
					if(kcreate_thread(dest_owner,port_table[m->dest_port].handler,stack_end + 4096)== -1){
						kfree(stack_end);
						kfree(message_data);
						report = ERROR;
					}
				} else{
					kfree(message_data);
					report = ERROR;
				}
			}
		} else { report = ERROR; }
	}
	if(report == ALL_OK){
		port_table[m->dest_port].msg_queue.tail = new_tail;
	}
	RELEASE_LOCK(&port_table_lock);
	return report;
}



int kretrieve_msg(struct message* m, int port_id){
	// Check if the port_id is in range
	if ((port_id >= MAX_NUMBER_OF_PORTS || port_id < 0)) {
		return ERROR;
	}
	LOCK4WRITE(&port_table_lock);
	const int port_owner = port_table[port_id].owner;
	// Check if the calling process is owner of the src_port.
	if(port_owner != kgetpid()){
		//kprints("Kernel: Not owner of port.\n\0");
		RELEASE_LOCK(&port_table_lock);
		return ERROR;
	}
	
	const int head = port_table[port_id].msg_queue.head;
	const int tail = port_table[port_id].msg_queue.tail;
	int report;
	int new_head;

	if (head == tail) { // If the queue is empty, no messages to return.
		report = ERROR;
	} else {
		if (head == MAX_NUMBER_OF_WAITING_MESSAGES - 1) {
			new_head = 0;
		} else {
			new_head = head + 1;
		}
		struct message msg = port_table[port_id].msg_queue.messages[head];
		*m = msg;
		port_table[port_id].msg_queue.head = new_head;
		report = ALL_OK;
	}
	RELEASE_LOCK(&port_table_lock);
	return report;
}